
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>键盘钢琴模拟器</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .piano-key {
            transition: all 0.1s ease;
            user-select: none;
        }
        .piano-key.white {
            background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
        }
        .piano-key.black {
            background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
            z-index: 10;
        }
        .piano-key.active {
            transform: translateY(2px);
            box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2);
        }
        .key-binding {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 0.75rem;
            color: #6b7280;
        }
        .note-display {
            animation: fadeOut 2s ease-out;
        }
        @keyframes fadeOut {
            0% { opacity: 1; transform: translateY(0); }
            100% { opacity: 0; transform: translateY(-20px); }
        }
    </style>
</head>
<body class="bg-gradient-to-br from-purple-50 to-blue-100 min-h-screen">
    <div class="container mx-auto px-4 py-8">
        <!-- 头部标题区域 -->
        <header class="text-center mb-12">
            <h1 class="text-4xl font-bold text-gray-800 mb-4">
                <i class="fas fa-music mr-3 text-purple-500"></i>
                键盘钢琴模拟器
            </h1>
            <p class="text-gray-600 text-lg max-w-2xl mx-auto">
                使用键盘按键演奏美妙的音乐！按下对应的字母键即可弹奏音符
            </p>
        </header>

        <!-- 主钢琴区域 -->
        <main class="max-w-6xl mx-auto">
            <!-- 音符显示区域 -->
            <div class="bg-white rounded-2xl shadow-lg p-6 mb-8">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-2xl font-semibold text-gray-800">
                当前音符
                </h2>
                <div class="flex items-center space-x-4">
                    <span class="text-sm text-gray-500">音量:</span>
                    <input type="range" id="volumeControl" min="0" max="100" value="80" 
                       class="w-32 accent-purple-500">
                </div>
            </div>

            <!-- 钢琴键盘 -->
            <div class="bg-gray-900 rounded-2xl shadow-2xl p-8 relative">
                <div class="flex justify-center relative" style="height: 200px;">
                    <!-- 白键 -->
                    <div class="flex">
                        <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="C4" data-key="A">
                        <div class="key-binding">A</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="D4" data-key="S">
                        <div class="key-binding">S</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="E4" data-key="D">
                        <div class="key-binding">D</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="F4" data-key="F">
                        <div class="key-binding">F</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="G4" data-key="G">
                        <div class="key-binding">G</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="A4" data-key="H">
                        <div class="key-binding">H</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="B4" data-key="J">
                        <div class="key-binding">J</div>
                    </div>
                    <div class="piano-key white w-16 h-48 rounded-b-lg border border-gray-300 relative cursor-pointer active:bg-gray-200" data-note="C5" data-key="K">
                        <div class="key-binding">K</div>
                    </div>
                </div>

                <!-- 黑键 -->
                <div class="absolute top-0 left-0 w-full" style="height: 120px;">
                    <div class="flex justify-center">
                        <div class="w-8"></div> <!-- 间隔 -->
                        <div class="piano-key black w-10 h-32 rounded-b-lg ml-4 cursor-pointer active:bg-gray-800" data-note="C#4" data-key="W">
                            <div class="key-binding text-white">W</div>
                        </div>
                        <div class="piano-key black w-10 h-32 rounded-b-lg ml-4 cursor-pointer active:bg-gray-800" data-note="D#4" data-key="E">
                            <div class="key-binding text-white">E</div>
                        </div>
                        <div class="w-16"></div> <!-- 白键E和F之间的间隔 -->
                        <div class="piano-key black w-10 h-32 rounded-b-lg ml-4 cursor-pointer active:bg-gray-800" data-note="F#4" data-key="T">
                            <div class="key-binding text-white">T</div>
                        </div>
                        <div class="piano-key black w-10 h-32 rounded-b-lg ml-4 cursor-pointer active:bg-gray-800" data-note="G#4" data-key="Y">
                            <div class="key-binding text-white">Y</div>
                        </div>
                        <div class="piano-key black w-10 h-32 rounded-b-lg ml-4 cursor-pointer active:bg-gray-800" data-note="A#4" data-key="U">
                            <div class="key-binding text-white">U</div>
                        </div>
                        <div class="w-16"></div> <!-- 最后一个间隔 -->
                    </div>
                </div>
            </div>

            <!-- 控制面板 -->
            <div class="bg-white rounded-2xl shadow-lg p-6 mt-8">
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div class="text-center">
                    <h3 class="font-semibold text-gray-700 mb-2">键盘映射</h3>
                    <div class="text-sm text-gray-600 space-y-1">
                        <div>白键: A S D F G H J K</div>
                        <div>黑键: W E T Y U</div>
                    </div>
                </div>
                <div class="text-center">
                    <h3 class="font-semibold text-gray-700 mb-2">演奏统计</h3>
                    <div id="stats" class="text-sm text-gray-600">
                        <div>已弹奏: <span id="notesPlayed">0</span> 个音符</div>
                    <div>当前音阶: <span id="currentOctave">4</span></div>
                </div>
                <div class="text-center">
                    <h3 class="font-semibold text-gray-700 mb-2">操作指南</h3>
                    <div class="text-sm text-gray-600">
                        <div>按下键盘对应字母键演奏</div>
                        <div>空格键: 停止所有声音</div>
                    </div>
                </div>
            </div>
        </main>

        <!-- 页脚 -->
        <footer class="text-center mt-12 text-gray-500">
            <p>使用键盘事件实现的交互式钢琴模拟器 | 2025</p>
        </footer>
    </div>

    <script src="script.js"></script>
</body>
</html>
<code_end>
<code_start project_name=键盘钢琴模拟器 filename=script.js title=钢琴交互逻辑 entrypoint=false runnable=false project_final_file=false>
class KeyboardPiano {
    constructor() {
        this.audioContext = null;
        this.oscillators = new Map();
        this.notesPlayed = 0;
        this.currentOctave = 4;
        this.volume = 0.8;
        
        this.init();
    }

    init() {
        this.setupEventListeners();
        this.setupAudioContext();
        this.updateDisplay();
    }

    setupAudioContext() {
        try {
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
        this.setupVolumeControl();
    }

    setupVolumeControl() {
        const volumeControl = document.getElementById('volumeControl');
        volumeControl.addEventListener('input', (e) => {
            this.volume = e.target.value / 100;
        });
    }

    setupEventListeners() {
        // 键盘按下事件
        document.addEventListener('keydown', (e) => {
            if (e.repeat) return; // 防止重复触发
            
            const key = e.key.toUpperCase();
            this.handleKeyPress(key, true);
        });

        // 键盘释放事件
        document.addEventListener('keyup', (e) => {
            const key = e.key.toUpperCase();
            this.handleKeyPress(key, false);
        });

        // 鼠标点击事件（备用）
        document.querySelectorAll('.piano-key').forEach(key => {
            key.addEventListener('mousedown', () => {
                const keyChar = key.getAttribute('data-key');
                this.handleKeyPress(keyChar, true);
            });

            key.addEventListener('mouseup', () => {
                const keyChar = key.getAttribute('data-key');
                this.handleKeyPress(keyChar, false);
            });

            key.addEventListener('mouseleave', () => {
                const keyChar = key.getAttribute('data-key');
                this.handleKeyPress(keyChar, false);
            });
        });
    }

    handleKeyPress(key, isPressed) {
        const keyElement = this.findKeyElement(key);
        
        if (isPressed) {
            if (key === ' ') {
                this.stopAllSounds();
                return;
            }
            
            if (keyElement) {
                this.playNote(keyElement, isPressed);
            }
        } else {
            if (keyElement) {
                this.stopNote(keyElement);
            }
        }
    }

    findKeyElement(key) {
        return document.querySelector(`[data-key="${key}"]`);
    }

    playNote(keyElement, isPressed) {
        if (!this.audioContext) return;

        const note = keyElement.getAttribute('data-note');
        
        // 添加激活状态
        keyElement.classList.add('active');
        
        // 创建振荡器
        const oscillator = this.audioContext.createOscillator();
        const gainNode = this.audioContext.createGain();
        
        oscillator.connect(gainNode);
        gainNode.connect(this.audioContext.destination);
        
        // 设置音色和频率
        oscillator.type = 'sine';
        oscillator.frequency.value = this.getFrequency(note);
        
        // 音量包络
        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
        gainNode.gain.linearRampToValueAtTime(this.volume, this.audioContext.currentTime + 0.01);
        
        oscillator.start();
        
        // 存储振荡器以便停止
        this.oscillators.set(note, { oscillator, gainNode });
        
        // 更新统计和显示
        this.notesPlayed++;
        this.updateDisplay(note);
    }

    stopNote(keyElement) {
        keyElement.classList.remove('active');
        
        const note = keyElement.getAttribute('data-note');
        const soundData = this.oscillators.get(note);
        
        if (soundData) {
            // 淡出效果
            soundData.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 0.1);
            
            setTimeout(() => {
                soundData.oscillator.stop();
                this.oscillators.delete(note);
            }, 100);
        }
    }

    stopAllSounds() {
        document.querySelectorAll('.piano-key').forEach(key => {
            key.classList.remove('active');
        });
        
        this.oscillators.forEach((soundData, note) => {
            soundData.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 0.1);
            
            setTimeout(() => {
                soundData.oscillator.stop();
            }, 100);
        });
        
        this.oscillators.clear();
    }

    getFrequency(note) {
        // 音符频率映射（以A4=440Hz为基准）
        const frequencies = {
            'C4': 261.63, 'C#4': 277.18,
            'D4': 293.66, 'D#4': 311.13,
            'E4': 329.63,
            'F4': 349.23, 'F#4': 369.99,
            'G4': 392.00, 'G#4': 415.30,
            'A4': 440.00, 'A#4': 466.16,
            'B4': 493.88,
            'C5': 523.25
        };
        
        return frequencies[note] || 440;
    }

    updateDisplay(currentNote = '') {
        // 更新统计信息
        document.getElementById('notesPlayed').textContent = this.notesPlayed;
        document.getElementById('currentOctave').textContent = this.currentOctave;
        
        // 显示当前音符（如果有）
        if (currentNote) {
            this.showNoteDisplay(currentNote);
        }
    }

    showNoteDisplay(note) {
        // 创建或更新音符显示
        let display = document.querySelector('.note-display');
        if (!display) {
            display = document.createElement('div');
            display.className = 'note-display fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-6xl font-bold text-purple-600 bg-white bg-opacity-90 rounded-2xl shadow-2xl p-8 text-center';
            document.body.appendChild(display);
        }
        
        display.textContent = note;
        display.style.opacity = '1';
        display.style.transform = 'translate(-50%, -50%)';
        
        // 淡出动画
        setTimeout(() => {
            display.style.opacity = '0';
            display.style.transform = 'translate(-50%, -70%)';
        }, 100);
        
        setTimeout(() => {
            if (display.parentNode) {
                display.parentNode.removeChild(display);
            }
        }, 2100);
    }
}

// 初始化钢琴应用
document.addEventListener('DOMContentLoaded', () => {
    new KeyboardPiano();
    
    // 添加键盘使用提示
    const helpText = document.createElement('div');
    helpText.className = 'fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-70 text-white px-4 py-2 rounded-lg text-sm';
    helpText.innerHTML = '按下 A S D F G H J K 键开始演奏 | 空格键停止';
    document.body.appendChild(helpText);
    
    setTimeout(() => {
        helpText.remove();
    }, 5000);
});
<code_end>
这个键盘钢琴模拟器具有以下功能和特点：
1. 完整的键盘事件处理系统，支持按键按下和释放的精确控制
2. Web Audio API实现真实音效，包含音量调节和淡入淡出效果
3. 美观的钢琴键盘UI，包含白键和黑键的视觉区分
4. 实时音符显示和演奏统计功能
5. 响应式设计确保在各种设备上都有良好的用户体验
6. 提供鼠标点击作为键盘操作的备用交互